﻿using UnityEngine;
using System.Collections;
using System.Net;
using System;
using System.Net.Sockets;
using System.Threading;
public class ClientSession
{
    public const int DefaultReceiveBufferSize = 4096;

    private bool m_InConnecting = false;
    Socket Client;

    private SocketAsyncEventArgs m_SocketEventArgs;
    private SocketAsyncEventArgs m_SocketEventArgsSend;
    protected ArraySegment<byte> Buffer { get; set; }

    public event EventHandler Connected;
    public event EventHandler<DataEventArgs> DataReceived;

    private ConcurrentBatchQueue<ArraySegment<byte>> m_SendingQueue;
    public int SendingQueueSize { get; set; }
    private int m_IsSending = 0;
    protected bool IsSending { get { return m_IsSending == 1; } }
    private PosList<ArraySegment<byte>> m_SendingItems;
    private PosList<ArraySegment<byte>> GetSendingItems()
    {
        if (m_SendingItems == null)
            m_SendingItems = new PosList<ArraySegment<byte>>();

        return m_SendingItems;
    }

    public void Connect(EndPoint endPoint)
    {
        if (m_InConnecting)
        {
            Debug.LogError("The socket is connecting, cannot connect again!");
            return;
        }
        if (this.Client != null)
        {
            Debug.LogError("The socket is connected, you needn't connect again!");
            return;
        }
        this.m_InConnecting = true;
        this.ConnectAsync(endPoint, this.ProcessConnect);
    }

    void ConnectAsync(EndPoint ipEndPoint, Action<Socket, SocketAsyncEventArgs> callBack)
    {
        var e = this.CreateSocketAsyncEventArgs(ipEndPoint, callBack);
        Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.ConnectAsync(e);
    }

    void ProcessConnect(Socket socket, SocketAsyncEventArgs e)
    {
        this.m_InConnecting = false;
        this.Client = socket;
        if (!socket.Connected)
        {
            this.m_InConnecting = false;
            Debug.LogError("连接失败" + e.SocketError.ToString());
            return;
        }
        Debug.Log("连接成功");
        if (this.Buffer.Array == null)
        {
            Buffer = new ArraySegment<byte>(new byte[DefaultReceiveBufferSize]);
        }
        e.SetBuffer(Buffer.Array, Buffer.Offset, Buffer.Count);
        m_SocketEventArgs = e;
        this.m_SocketEventArgs.Completed += SocketEventArgsCompleted;

        if (this.Connected != null)
        {
            this.Connected(this, EventArgs.Empty);
        }
        this.StartReceive();
    }

    private void SocketEventArgsCompleted(object sender, SocketAsyncEventArgs e)
    {
        if (e.LastOperation == SocketAsyncOperation.Connect)
        {
            ProcessConnect(sender as Socket, e);
            return;
        }
        ProcessReceive(e);
    }

    SocketAsyncEventArgs CreateSocketAsyncEventArgs(EndPoint ipEndPoint, Action<Socket, SocketAsyncEventArgs> callBack)
    {
        SocketAsyncEventArgs NewE = new SocketAsyncEventArgs();
        NewE.UserToken = callBack;
        NewE.RemoteEndPoint = ipEndPoint;
        NewE.Completed += new EventHandler<SocketAsyncEventArgs>(SocketAsyncEventCompleted);
        return NewE;
    }

    private void SocketAsyncEventCompleted(object sender, SocketAsyncEventArgs e)
    {
        e.Completed -= SocketAsyncEventCompleted;
        Action<Socket, SocketAsyncEventArgs> callBack = (Action<Socket, SocketAsyncEventArgs>)e.UserToken;
        e.UserToken = null;
        callBack(sender as Socket, e);
    }

    void StartReceive()
    {
        try
        {
            this.Client.ReceiveAsync(this.m_SocketEventArgs);
        }
        catch (SocketException exc)
        {
            Debug.LogError(exc.ToString());
            this.Client.Shutdown(SocketShutdown.Both);
        }
        catch (Exception e)
        {
            Debug.LogError(e.ToString());
            this.Client.Shutdown(SocketShutdown.Both);
        }

    }

    private void ProcessReceive(SocketAsyncEventArgs e)
    {
        if (e.SocketError != SocketError.Success || e.BytesTransferred == 0)
        {
            this.Client.Shutdown(SocketShutdown.Both);
            return;
        }

        OnDataReceived(e.Buffer, e.Offset, e.BytesTransferred);
        StartReceive();
    }

    private DataEventArgs m_DataArgs = new DataEventArgs();
    protected virtual void OnDataReceived(byte[] data, int offset, int length)
    {
        if (this.DataReceived != null)
        {
            m_DataArgs.Data = data;
            m_DataArgs.Offset = offset;
            m_DataArgs.Length = length;
            this.DataReceived(this, m_DataArgs);
        }
    }

    public void Close()
    {
        if (this.Client != null && this.Client.Connected)
        {
            this.Client.Shutdown(SocketShutdown.Both);
            this.Client.Close();
        }
        if (this.m_SocketEventArgs != null)
        {
            this.m_SocketEventArgs.Dispose();
        }
    }

    #region Send
    public void Send(ArraySegment<byte> segment)
    {
        if (segment == null || segment.Count == 0)
        {
            Debug.LogError("发送字节不能为空");
            return;
        }
        if (TrySend(segment))
        {
            return;
        }
        while (true)
        {
            Thread.SpinWait(1);
            if (TrySend(segment))
            {
                return;
            }
        }
    }

    public bool TrySend(ArraySegment<byte> segment)
    {
        bool isEnqueued = GetSendingQueue().Enqueue(segment);
        if (Interlocked.CompareExchange(ref m_IsSending, 1, 0) != 0)
        {
            return isEnqueued;
        }

        this.DequeueSend();
        return isEnqueued;
    }

    private ConcurrentBatchQueue<ArraySegment<byte>> GetSendingQueue()
    {
        if (this.m_SendingQueue != null)
        {
            return m_SendingQueue;
        }
        lock (this)
        {
            if (m_SendingQueue != null)
            {
                return m_SendingQueue;
            }
            m_SendingQueue = new ConcurrentBatchQueue<ArraySegment<byte>>(Math.Max(SendingQueueSize, 1024), (t) => t.Array == null || t.Count == 0);
            return m_SendingQueue;
        }
    }
    private void DequeueSend()
    {
        PosList<ArraySegment<byte>> sendingItems = GetSendingItems();
        if (!m_SendingQueue.TryDequeue(sendingItems))
        {
            m_IsSending = 0;
            return;
        }
        this.SendInternal(sendingItems);
    }

    protected  void SendInternal(PosList<ArraySegment<byte>> items)
    {
        if (m_SocketEventArgsSend == null)
        {
            m_SocketEventArgsSend = new SocketAsyncEventArgs();
            m_SocketEventArgsSend.Completed += new EventHandler<SocketAsyncEventArgs>(Sending_Completed);
        }

        if (items.Count > 1)
        {
            m_SocketEventArgsSend.SetBuffer(null, 0, 0);
            m_SocketEventArgsSend.BufferList = items;
        }
        else
        {
            ArraySegment<byte> currentItem = items[0];
            m_SocketEventArgsSend.SetBuffer(currentItem.Array, 0, currentItem.Count);
        }
       bool raiseEvent =  Client.SendAsync(m_SocketEventArgsSend);
        if (!raiseEvent)
        {
            Sending_Completed(Client, m_SocketEventArgsSend);
        }
    }
    void Sending_Completed(object sender, SocketAsyncEventArgs e)
    {
        if (e.SocketError != SocketError.Success || e.BytesTransferred == 0)
        {
            Debug.LogError("发包失败"+e.SocketError.ToString());
            this.Close();
            return;
        }
        this.OnSendingCompleted();
    }

    protected void OnSendingCompleted()
    {
        var sendingItems = GetSendingItems();
        sendingItems.Clear();
        sendingItems.Position = 0;

        if (!m_SendingQueue.TryDequeue(sendingItems))
        {
            m_IsSending = 0;
            return;
        }
        SendInternal(sendingItems);
    }
    #endregion
}
